#!/usr/bin/python3

# sudo apt-get install python3-tk

"""
会导致出现 emulator-5558   offline 这样的设备
adb connect 127.0.0.1:1111 && adb devices

adb shell input -d 66 tap 30 30
adb shell input -d 66 text adb测试信息


"""

import copy
import tkinter
import socket
import io
import signal, os
import subprocess
import threading
import time
from queue import Queue
from tkinter import messagebox
from PIL import Image, ImageTk

# https://pillow.readthedocs.io/en/stable/reference/Image.html

adbport=1111
adbconn="127.0.0.1:"+str(adbport)
app=None
msDelay016 =16
msDelay033 =33
msDelaySlow=500
QUEUE_SIZE=1024
nFrameCache=0
lkImage = threading.Lock()
bytesImg=None
exit_request=False
SIG_SCREEN_DIRTY=signal.SIGRTMIN + 8
eNotify = threading.Event()
qNotify = Queue(QUEUE_SIZE)


def msNow():
    return int(round(time.time() * 1000))

def shellexec(strcmd):
    strout = ""
    try:
        # print("正在运行指令：" + strcmd)
        process = subprocess.Popen(strcmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) #, cwd=os.environ['ZZSWMGR_WORK_DIR'], env={'ZZSWMGR_WORK_DIR':os.environ['ZZSWMGR_WORK_DIR']})
        # process.stdin.close()
        # process.stdout.close()
        process.wait()
        strout = process.stdout.read().decode("utf8")
        # print(strout)
        return process.returncode,strout
    except Exception as e:
        print("" + str(e))
        return -1,strout



class TGetScrImg(threading.Thread):
    def __init__(self, app, pipepath):
        threading.Thread.__init__(self)
        self.image = None
        self.imgseq = 999
        self.imgupd = msNow()
        self.dirtyNotifyPipe = pipepath
        self.app = app

    def getImgViaUDSock(self):
        global lkImage
        global bytesImg
        try:
            
            # unix domain sockets 连接写法
            server_address = '/exbin/ipc/virdisp.sock'
            socket_family = socket.AF_UNIX
            socket_type = socket.SOCK_STREAM

            # 其他代码完全一样
            sock = socket.socket(socket_family, socket_type)
            sock.connect(server_address)

            # print("self.imgseq: " + str(self.imgseq))
            bSeq = self.imgseq.to_bytes(length=4, byteorder='little', signed=False)
            sock.send(bSeq)

            magic = sock.recv(9)
            tmpseq= sock.recv(4)
            tmplen= sock.recv(4)
            # pkglen= int().from_bytes(tmplen, byteorder='big', signed=False)
            imgseq= int().from_bytes(tmpseq, byteorder='little', signed=False)
            pkglen= int().from_bytes(tmplen, byteorder='little', signed=False)
            # print("pkglen: " + str(pkglen))
            if pkglen > 0:
                self.data = bytes()
                while True:  
                    recv = sock.recv(1024*200)
                    if not recv:  
                        break
                    self.data = self.data + recv

                # ioio = io.BytesIO(self.data)
                # iimg = Image.open(ioio)
                # pilImage = iimg.resize((self.app.image_width, self.app.image_height), Image.LANCZOS)
                # self.image = ImageTk.PhotoImage(image=pilImage)

                # print("self.imgseq: " + str(self.imgseq))
                self.imgseq = imgseq
                self.imgupd = msNow()
            else:
                self.image = None

            # 关闭 UDS 连接
            sock.close()

            # print(f"image: '{self.image}'")   # todo
        except Exception as e:
            self.image = None
            pass
            # print("图片接收出错")s
            # print(e)

        # print("TGetScrImg.data:")
        # print(self.data)
        lkImage.acquire()
        bytesImg = self.data
        lkImage.release()


    # 屏幕有变化就通知的这个函数有点难搞，用管道画面会卡顿，用进程间信号又有信号丢失的情况
    def run(self):
        global app
        global eNotify
        global qNotify
        global nFrameCache
        global exit_request
        pin = None
        print("屏幕传送线程 已启动")

        try:
            while not exit_request:
                # 信号量模式
                if True:
                    data = qNotify.get()
                    # eNotify.wait()
                    # eNotify.clear()
                    if exit_request:
                        # print("break")
                        break
                    # print("getImgViaUDSock")
                    self.getImgViaUDSock()
                    if app != None:
                        # print("app.event_generate i")
                        # app.image = self.image
                        app.event_generate('<<SIG_SCREEN_DIRTY>>')
                        # print("app.event_generate o")

                # 管道模式, 画面会卡顿
                else:
                    data=None
                    if pin == None:
                        pin = os.open(dirtyNotifyPipe, os.O_RDONLY|os.O_NONBLOCK )
                    try:
                        data = os.read(pin, 1)
                    except Exception as e:
                        # print("管道读取出错")
                        print(e)
                        time.sleep(133/1000)
                        continue
                    if data != None and len(data)>0 and app != None:
                        app.event_generate('<<SIG_SCREEN_DIRTY>>')
                    # else:
                    #     os.close(pin)
                    #     pin = None
                    #     time.sleep(33/1000)
                    # # time.sleep(1)
        except Exception as e:
            # print("loopReadDiryPipe 出错")
            print(e)
        finally:
            if pin != None:
                try:
                    os.close(pin)
                except Exception as ee:
                    pass

        print("屏幕传送线程 已退出")



class App(tkinter.Tk):
    def __init__(self):
        tkinter.Tk.__init__(self)
        self.resizable(width=False, height=False)
        
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.bind('<Configure>', self.on_win_configured)
        self.bind('<Map>',   self.on_win_map)
        self.bind('<Unmap>', self.on_win_unm)
        self.bind("<<SIG_SCREEN_DIRTY>>", self.on_SIG_SCREEN_DIRTY, add='+')
        signal.signal(SIG_SCREEN_DIRTY, self.sigHandler)

        self.title('安卓桌面')
        # self.geometry('768x1367')
        p1 = ImageTk.PhotoImage(file = '/exbin/tools/zzswmgr/appicons/android.jpg') 
        self.iconphoto(False, p1)

        self.image_width = 768   #756
        self.image_height = 1367 #1680
        self.canvas = tkinter.Canvas(self, bg='white', width=self.image_width, height=self.image_height)  # 绘制画布
        self.canvas.pack(side=tkinter.LEFT)

        self.bFrame = tkinter.Frame(bd=0)
        self.bFrame.pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=True)

        self.btnConn=tkinter.Button(self.bFrame, height = 3, text='设备连接', relief='flat', command=None)
        self.btnConn.pack(padx=0, pady=5, )
        tmpbtn=tkinter.Button(self.bFrame, height = 3, text='打开QQ', relief='flat', command=lambda: self.delay_start_app("com.tencent.mobileqq", ".activity.SplashActivity"))
        tmpbtn.pack(padx=0, pady=5, )
        tmpbtn=tkinter.Button(self.bFrame, height = 3, text='打开微信', relief='flat', command=lambda: self.delay_start_app("com.tencent.mm", ".ui.LauncherUI"))
        tmpbtn.pack(padx=0, pady=5, )
        tmpbtn=tkinter.Button(self.bFrame, height = 3, text='打开抖音', relief='flat', command=lambda: self.delay_start_app("com.ss.android.ugc.aweme", ".splash.SplashActivity"))
        tmpbtn.pack(padx=0, pady=5, )
        tmpbtn=tkinter.Button(self.bFrame, height = 3, text='打开B站', relief='flat', command=lambda: self.delay_start_app("tv.danmaku.bili", ".MainActivityV2"))
        tmpbtn.pack(padx=0, pady=5, )


        self.image = None

        self.vdisplayid=""
        self.ctrlInit()

        self.t = None
        self.NOTIFY_PIPE=os.getenv('NOTIFY_PIPE')
        self.pid = os.getpid()
        self.dirtyNotifyPipe = "/exbin/tmp/virtDisplayNtPipe-{0}".format(self.pid)
        shellexec("rm -rf    /exbin/tmp/virtDisplayNtPipe-*")
        shellexec("mkfifo    {0}".format(self.dirtyNotifyPipe))
        shellexec("chmod 666 {0}".format(self.dirtyNotifyPipe))
        shellexec("echo \"#virtDisplaySetX11Pid {0} {1}\" > \"{2}\"".format(self.pid, SIG_SCREEN_DIRTY, self.NOTIFY_PIPE))

        self.default_interval = msDelay033 # msDelay016 # msDelay033
        self.ms_refresh_interval = self.default_interval
        self.imgseq = 999
        self.imgupd = msNow()

        # 启动屏幕获取线程
        self.t=TGetScrImg(self, self.dirtyNotifyPipe)
        self.t.start()
        # self.after(self.ms_refresh_interval, self.timer)

        self.strRecentPkg = ""
        self.rendering=False
        self.Updating=False
        self.after_idle(self.startDefaultApp)


    def on_closing(self):
        global app
        global exit_request
        global eNotify
        global qNotify

        app = None
        exit_request=True
        if self.strRecentPkg != "" and self.strRecentPkg != "com.zzvm":
            shrlt,strout = shellexec("adb -s " + adbconn + " shell am force-stop " + self.strRecentPkg)
        shellexec("echo \"#virtDisplaySetX11Pid {0} {1}\" > \"{2}\"".format(0, 0, self.NOTIFY_PIPE))

        # 退出 loopReadDiryPipe 线程
        # eNotify.set()
        if self.t != None:
            qNotify.put(1)
            self.after(100, self.t.join)
        self.after(200, self.destroy)

    def on_win_configured(self,event):
        pass
    def on_win_map(self,event):
        if not self.rendering and self.vdisplayid != "":
            self.rendering=True
            self.imgupd = msNow()
    def on_win_unm(self,event):
        self.rendering=False
    def sigHandler(self, signum, frame):
        global app
        global nFrameCache
        global eNotify
        global qNotify
        global exit_request

        # print('收到信号: ', signum)

        if exit_request:
            return

        if SIG_SCREEN_DIRTY == signum:
            qsize = qNotify.qsize()
            # print("     sigHandler 中 qNotify.qsize(): " + str(qsize))
            if qsize < 10:
                for i in range(10):
                    qNotify.put(i)
            # eNotify.set()
            pass
        else:
            # print('Received signal: ', signum)
            pass
    def on_SIG_SCREEN_DIRTY(self, evnet):
        self.UpdateImage()

    def ctrlInit(self):
        self.canvas.bind("<Button-1>", self.onLBtnDown)
    def onLBtnDown(self, event):
        print(str(event.x) + " " + str(event.y))
        shrlt,strout=shellexec("adb -s " + adbconn + " shell -- input -d " + self.vdisplayid + " tap " + str(event.x) + " " + str(event.y))

    def createAndroidVirDisplay(self):
        try:
            shrlt,strout = shellexec("adb -s " + adbconn + " shell dumpsys display|grep \"type=EXTERNAL\"")
            # print("shrlt: " + str(shrlt))
            if shrlt == 0:
                return False

            # dpi 调整后，需要重启虚拟电脑
            tmpDPI=144
            tmpDPI=-1
            shrlt,strout = shellexec("echo \"#createAndroidVirtDisplay {0} {1} {2}\" > \"{3}\"".format(self.image_width, self.image_height, tmpDPI, self.NOTIFY_PIPE))
            if shrlt != 0:
                return False
            return True
        except Exception as e:
            print(e)
            return False
    def getAndroidVirDisplayId(self):
        try:
            shrlt,strout=shellexec("adb -s " + adbconn + " shell dumpsys window displays|grep mDisplayId|grep -v mDisplayId=0|grep -v \"organized\"|head -n 1|awk '{ printf($2);  }'|cut -c 12-")
            if shrlt != 0:
                return False
            self.vdisplayid=strout.replace('\n', '').replace('\r', '')
            if self.vdisplayid == "":
                return False
            return True
        except Exception as e:
            print(e)
            return False
    def delay_start_app(self, strpkg, stract):
        self.after_idle(self.startapp, strpkg, stract)
        # self.startapp(strpkg, stract)
    def startapp(self, strpkg, stract):
        strapp = strpkg + "/" + stract
        try:
            shrlt,strout = shellexec("adb connect 127.0.0.1:" + str(adbport))
            if shrlt != 0:
                messagebox.showinfo("错误", "adb连接失败\n请先运行桌面上的adbme，并按步骤连接设备")
                return False
            
            if not self.createAndroidVirDisplay():
                messagebox.showinfo("错误", "无法创建安卓虚拟桌面")
                return False
            
            if not self.getAndroidVirDisplayId():
                messagebox.showinfo("错误", "无法获取安卓虚拟桌面id")
                return False

            # 先结束上次启动的应用，不然开的进程太多，容易导致安卓杀进程
            if self.strRecentPkg != "" and self.strRecentPkg != "com.zzvm":
                shrlt,strout = shellexec("adb -s " + adbconn + " shell am force-stop " + self.strRecentPkg)

            # shrlt,strout = shellexec("/exbin/tools/vm_createAndroidVirtDisplay2.sh " + strapp)
            shrlt,strout = shellexec("adb -s {0} shell am start --display {1} -S {2}".format(adbconn, self.vdisplayid, strapp))
            if shrlt != 0:
                messagebox.showinfo("错误", strpkg + "\napp启动失败，请重试")
                return False
            else:
                self.strRecentPkg = strpkg
                for i in range(50):
                    qNotify.put(i)
                # print("startDefaultApp 中 qNotify.qsize(): " + str(qNotify.qsize()))

            return True
        except Exception as e:
            messagebox.showinfo("错误", strpkg + "\napp启动失败，请重试")
            print(e)
            return False
    def startDefaultApp(self):
        global qNotify
        global QUEUE_SIZE
        # if self.startapp("com.zzvm", ".VirtActivity"):
        # if self.startapp("com.draco.extlaunch", ".views.MainActivity"):
        # if self.delay_start_app("com.ss.android.ugc.aweme", ".splash.SplashActivity"):

        self.strRecentPkg="com.tencent.mm"
        if self.startapp(self.strRecentPkg, ".ui.LauncherUI"):
        # self.strRecentPkg="com.ss.android.ugc.aweme"
        # if self.startapp(self.strRecentPkg, ".splash.SplashActivity"):
            self.rendering=True

    def timer(self):
        # qsize = qNotify.qsize()
        # if qsize < 50:
        #     for i in range(5):
        #         qNotify.put(i)
        # # self.UpdateImage()
        # # # print("self.ms_refresh_interval: " + str(self.ms_refresh_interval))
        self.after(self.ms_refresh_interval, self.timer)

    # 显示安卓屏幕画面
    def UpdateImage(self):
        global lkImage

        # print("UpdateImage")

        # # 显示当前线程名称
        # print(threading.current_thread().name)

        if self.Updating:
            print("更新画面的过程中又来一帧新的画面，新画面将被忽略")
            return

        # # 显示正在运行的进程列表
        # print(threading.enumerate())
        # print(self.state())

        # print("update image")
        if self.rendering:
            self.Updating = True

            if bytesImg == None:
                self.Updating = False
                return

            iimg = None
            # 互斥部分，获取图片数据
            lkImage.acquire()
            try:
                ioio = io.BytesIO(bytesImg)
                iimg = Image.open(ioio)
            except Exception as e:
                iimg = None
                print(e)
                self.Updating = False
                return
            finally:
                lkImage.release()

            # 图片数据转成图片，然后显示
            if iimg != None:
                try:
                    # 调整图片大小
                    if False:
                        pilImage = iimg.resize((self.image_width, self.image_height), Image.LANCZOS)
                        self.image = ImageTk.PhotoImage(image=pilImage)
                    else:
                        self.image = ImageTk.PhotoImage(image=iimg)
                    self.canvas.create_image(0, 0, anchor=tkinter.NW, image=self.image)
                except Exception as ee:
                    print(ee)

        self.Updating = False
        # time.sleep(0.001)




if __name__ == "__main__":
    app=App()
    app.mainloop()

    # # 显示正在运行的进程列表
    # print(threading.enumerate())


